﻿using System;
using System.Data;
using System.Data.SqlClient;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Data.Common;

namespace Libreria.DAL
{
    /// <summary>
    /// Description of MOR.
    /// </summary>
    public class MOR
    {
        // Atributos del patron Singleton
        private static volatile MOR instance;
        private static object syncRoot = new Object();

        // Atributos de la clase
        private string cDeConexion;
        private SqlConnection oConn;

        // Constructores
        private MOR(string cadenaDeConexion)
        {
            if (cadenaDeConexion != null)
            {
                this.cDeConexion = cadenaDeConexion;
                this.oConn = new SqlConnection(cadenaDeConexion);
            }
        }

        // Métodos de acceso a los datos
        /// <summary>
        /// Método para consultar un conjunto de registros
        /// </summary>
        /// <param name="modelo">Objeto persistido</param>
        /// <param name="porLlavePrimaria">si la busqueda se desae realizar teniendo en cuenta la llave primaria del objeto</param>
        /// <returns></returns>
        public List<Object> consultarRegistros(Object modelo, bool porLlavePrimaria) {
            return consultarRegistros(modelo, porLlavePrimaria, 0, false);
        }

        /// <summary>
        /// Método para consultar un conjunto de registros especificando el número de registros a consultar
        /// </summary>
        /// <param name="modelo">Objeto persistido</param>
        /// <param name="porLlavePrimaria">si la busqueda se desae realizar teniendo en cuenta la llave primaria del objeto</param>
        /// <param name="top">Especifica el número de registros que se desea de la consulta</param>
        /// <param name="descendente">Verdadero si se quiere que los datos se deveulvan en orden descendente</param>
        /// <returns></returns>
        public List<Object> consultarRegistros(Object modelo, bool porLlavePrimaria, int top, bool descendente)
        {
            Type tipo = modelo.GetType();

            System.Attribute[] attrs = System.Attribute.GetCustomAttributes(modelo.GetType());  // reflection
            string consultaNombreTabla = null;
            string consultaValidacion = null;
            Dictionary<string, Object> datos = new Dictionary<string, object>();

            foreach (System.Attribute attr in attrs)
            {
                if (attr is CAD)
                {
                    CAD a = (CAD)attr;
                    if (a.EsModelo)
                    {
                        consultaNombreTabla = (a.NombreTablaIgualNombreClase) ? tipo.Name : a.NombreTabla;
                        PropertyInfo[] campos = tipo.GetProperties();
                        Boolean sw = false;
                        foreach (PropertyInfo campo in campos)
                        {
                            if (campo.GetValue(modelo, null) != null)
                            {
                                Tipo datoAttr = (Tipo)System.Attribute.GetCustomAttribute(campo, typeof(Libreria.DAL.Tipo));
                                if (datoAttr != null)
                                {
                                    if (this.validaTipoValor(datoAttr.SsTipo, campo, modelo))
                                    {
                                        if (porLlavePrimaria)
                                        {
                                            if (datoAttr.EsLlavePrimaria)
                                            {
                                                string nombre = this.validarNombrecampo(campo, datoAttr);
                                                consultaValidacion += nombre + " = @" + nombre;
                                                datos.Add("@" + nombre, campo.GetValue(modelo, null));
                                            }

                                        }
                                        else
                                        {
                                            if (!campo.GetValue(modelo, null).Equals(0) && !campo.GetValue(modelo, null).Equals(0.0))
                                            {
                                                string nombre = this.validarNombrecampo(campo, datoAttr);
                                                consultaValidacion += nombre + " = @" + nombre + " AND ";
                                                datos.Add("@" + nombre, campo.GetValue(modelo, null));
                                                sw = true;
                                            }
                                        }
                                    }
                                    else
                                    {
                                        if (porLlavePrimaria)
                                        {
                                            if (datoAttr.EsLlavePrimaria)
                                            {
                                                throw new Exception("El  de dato para: " + campo.Name + ", es obligatorio por pertenecer al identificador del registro.");
                                            }
                                        }
                                    }
                                }
                            }

                        }
                        if (sw)
                        {
                            consultaValidacion = consultaValidacion.Remove(consultaValidacion.Length - 4);
                        }
                    }
                }
            }

            string consulta = "";
            if (top == 0)
            {
                consulta = "SELECT * FROM " + consultaNombreTabla;
            }
            else
            {
                consulta = "SELECT TOP " + top + " * FROM " + consultaNombreTabla;
            }
            if (datos.Count > 0)
            {
                consulta += " WHERE " + consultaValidacion;
            }
            if (descendente)
            {
                consulta += " ORDER BY 1 DESC";
            }

            //return this.ejecutarConsultaSQL(consulta, datos);

            //return this.ejecutarProcedimientoSQL("selectSede", datos);
            return this.ejecutarConsultaSQL(consulta, datos, modelo.GetType());
        }

        /// <summary>
        /// Método para borrar un registro en la persitencia del objeto.
        /// </summary>
        /// <param name="modelo">Objeto a persistir</param>
        /// <returns></returns>
        public int borrarRegistro(Object modelo)
        {
            Type tipo = modelo.GetType();

            System.Attribute[] attrs = System.Attribute.GetCustomAttributes(modelo.GetType());  // reflection
            string consultaNombreTabla = null;
            string consultaValidacion = null;
            Dictionary<string, Object> datos = new Dictionary<string, object>();

            foreach (System.Attribute attr in attrs)
            {
                if (attr is CAD)
                {
                    CAD a = (CAD)attr;
                    if (a.EsModelo)
                    {
                        consultaNombreTabla = (a.NombreTablaIgualNombreClase) ? tipo.Name : a.NombreTabla;
                        PropertyInfo[] campos = tipo.GetProperties();
                        foreach (PropertyInfo campo in campos)
                        {
                            if (campo.GetValue(modelo, null) != null)
                            {
                                Tipo datoAttr = (Tipo)System.Attribute.GetCustomAttribute(campo, typeof(Libreria.DAL.Tipo));
                                if (datoAttr != null)
                                {
                                    if (this.validaTipoValor(datoAttr.SsTipo, campo, modelo))
                                    {
                                        string nombre = this.validarNombrecampo(campo, datoAttr);
                                        consultaValidacion += nombre + " = @" + nombre + " AND ";
                                        datos.Add("@" + nombre, campo.GetValue(modelo, null));
                                    }
                                }
                            }

                        }
                        consultaValidacion = consultaValidacion.Remove(consultaValidacion.Length - 4);
                    }
                }
            }

            string consulta = "DELETE FROM " + consultaNombreTabla + " WHERE " + consultaValidacion;

            return this.ejecutarOperacionSQL(consulta, datos, false);
        }

        /// <summary>
        /// Método para actualizar la persitencia del objeto.
        /// </summary>
        /// <param name="modelo">Objeto a persistir</param>
        /// <returns></returns>
        public int actualizarRegistro(Object modelo)
        {
            return actualizarRegistro(modelo, false);
        }

        /// <summary>
        /// Método para actualizar la persitencia del objeto.
        /// </summary>
        /// <param name="modelo">Objeto a persistir</param>
        /// <returns></returns>
        public int actualizarRegistro(Object modelo, Boolean withoutPrimaryKey)
        {
            Type tipo = modelo.GetType();

            System.Attribute[] attrs = System.Attribute.GetCustomAttributes(modelo.GetType());  // reflection
            string consultaNombreTabla = null;
            string consultaCampos = null;
            string consultaValidacion = null;
            bool existeLlavePrimaria = false;
            Dictionary<string, Object> datos = new Dictionary<string, object>();

            foreach (System.Attribute attr in attrs)
            {
                if (attr is CAD)
                {
                    CAD a = (CAD)attr;
                    if (a.EsModelo)
                    {
                        consultaNombreTabla = (a.NombreTablaIgualNombreClase) ? tipo.Name : a.NombreTabla;
                        PropertyInfo[] campos = tipo.GetProperties();
                        foreach (PropertyInfo campo in campos)
                        {
                            if (campo.GetValue(modelo, null) != null)
                            {
                                Tipo datoAttr = (Tipo)System.Attribute.GetCustomAttribute(campo, typeof(Libreria.DAL.Tipo));
                                if (datoAttr != null)
                                {
                                    if (this.validaTipoValor(datoAttr.SsTipo, campo, modelo))
                                    {
                                        string nombre = this.validarNombrecampo(campo, datoAttr);
                                        if (datoAttr.EsLlavePrimaria)
                                        {
                                            existeLlavePrimaria = true;
                                            consultaValidacion += nombre + " = @" + nombre + " AND ";
                                        }
                                        else
                                        {
                                            consultaCampos += nombre + " = @" + nombre + ", ";
                                        }
                                        datos.Add("@" + nombre, campo.GetValue(modelo, null));
                                    }
                                    else
                                    {
                                        if (datoAttr.EsLlavePrimaria && !withoutPrimaryKey)
                                        {
                                            throw new Exception("El  de dato para: " + campo.Name + ", es obligatorio por pertenecer al identificador del registro.");
                                        }
                                    }
                                }
                            }

                        }
                        if (existeLlavePrimaria)
                        {
                            consultaCampos = consultaCampos.Remove(consultaCampos.Length - 2);
                            consultaValidacion = consultaValidacion.Remove(consultaValidacion.Length - 4);
                        }
                        else
                        {
                            throw new Exception("El de registro no tiene definido el identificador.");
                        }
                    }
                }
            }

            string consulta = "UPDATE " + consultaNombreTabla + " SET " + consultaCampos + " WHERE " + consultaValidacion;

            return this.ejecutarOperacionSQL(consulta, datos, false);
        }

        /// <summary>
        /// Método para actualizar la persitencia del objeto.
        /// </summary>
        /// <param name="modelo">Objeto a persistir</param>
        /// <returns></returns>
        public int actualizarRegistroConNulos(Object modelo)
        {
            return actualizarRegistro(modelo, false);
        }

        /// <summary>
        /// Método para actualizar la persitencia del objeto.
        /// </summary>
        /// <param name="modelo">Objeto a persistir</param>
        /// <returns></returns>
        public int actualizarRegistroConNulos(Object modelo, Boolean withoutPrimaryKey)
        {
            Type tipo = modelo.GetType();

            System.Attribute[] attrs = System.Attribute.GetCustomAttributes(modelo.GetType());  // reflection
            string consultaNombreTabla = null;
            string consultaCampos = null;
            string consultaValidacion = null;
            bool existeLlavePrimaria = false;
            Dictionary<string, Object> datos = new Dictionary<string, object>();

            foreach (System.Attribute attr in attrs)
            {
                if (attr is CAD)
                {
                    CAD a = (CAD)attr;
                    if (a.EsModelo)
                    {
                        consultaNombreTabla = (a.NombreTablaIgualNombreClase) ? tipo.Name : a.NombreTabla;
                        PropertyInfo[] campos = tipo.GetProperties();
                        foreach (PropertyInfo campo in campos)
                        {
                            Tipo datoAttr = (Tipo)System.Attribute.GetCustomAttribute(campo, typeof(Libreria.DAL.Tipo));
                            if (datoAttr != null)
                            {
                                if (this.validaTipoValor(datoAttr.SsTipo, campo, modelo))
                                {
                                    string nombre = this.validarNombrecampo(campo, datoAttr);
                                    if (datoAttr.EsLlavePrimaria)
                                    {
                                        existeLlavePrimaria = true;
                                        consultaValidacion += nombre + " = @" + nombre + " AND ";
                                    }
                                    else
                                    {
                                        consultaCampos += nombre + " = @" + nombre + ", ";
                                    }
                                    datos.Add("@" + nombre, campo.GetValue(modelo, null));
                                }
                                else
                                {
                                    if (datoAttr.EsLlavePrimaria && !withoutPrimaryKey)
                                    {
                                        throw new Exception("El  de dato para: " + campo.Name + ", es obligatorio por pertenecer al identificador del registro.");
                                    }
                                    else
                                    {
                                        string nombre = this.validarNombrecampo(campo, datoAttr);
                                        if (datoAttr.EsLlavePrimaria)
                                        {
                                            existeLlavePrimaria = true;
                                            consultaValidacion += nombre + " = @" + nombre + " AND ";
                                        }
                                        else
                                        {
                                            consultaCampos += nombre + " = @" + nombre + ", ";
                                        }
                                        datos.Add("@" + nombre, DBNull.Value);
                                    }
                                }
                            }

                        }
                        if (existeLlavePrimaria)
                        {
                            consultaCampos = consultaCampos.Remove(consultaCampos.Length - 2);
                            consultaValidacion = consultaValidacion.Remove(consultaValidacion.Length - 4);
                        }
                        else
                        {
                            throw new Exception("El de registro no tiene definido el identificador.");
                        }
                    }
                }
            }

            string consulta = "UPDATE " + consultaNombreTabla + " SET " + consultaCampos + " WHERE " + consultaValidacion;

            return this.ejecutarOperacionSQL(consulta, datos, false);
        }

        /// <summary>
        /// Método para persistir el dato
        /// </summary>
        /// <param name="modelo">Objeto a persistir</param>
        /// <returns></returns>
        public Int16 crearRegistro(Object modelo)
        {
            Type tipo = modelo.GetType();

            System.Attribute[] attrs = System.Attribute.GetCustomAttributes(modelo.GetType());  // reflection
            string consultaNombreTabla = null;
            string consultaCampos = null;
            string consultaValores = null;
            bool laPrimariaEsidentidad = false;
            Dictionary<string, Object> datos = new Dictionary<string, object>();

            foreach (System.Attribute attr in attrs)
            {
                if (attr is CAD)
                {
                    CAD a = (CAD)attr;
                    if (a.EsModelo)
                    {
                        consultaNombreTabla = (a.NombreTabla != null) ? a.NombreTabla : tipo.Name;
                        PropertyInfo[] campos = tipo.GetProperties();
                        foreach (PropertyInfo campo in campos)
                        {
                            Tipo datoAttr = (Tipo)System.Attribute.GetCustomAttribute(campo, typeof(Libreria.DAL.Tipo));
                            if (datoAttr != null)
                            {
                                if (!datoAttr.EsIdentidad)
                                {
                                    if (campo.GetValue(modelo, null) != null)
                                    {
                                        if (this.validaTipoValor(datoAttr.SsTipo, campo, modelo))
                                        {
                                            string nombre = this.validarNombrecampo(campo, datoAttr);
                                            consultaCampos += nombre + ", ";
                                            consultaValores += "@" + nombre + ", ";
                                            datos.Add("@" + nombre, campo.GetValue(modelo, null));
                                        }
                                        else
                                        {
                                            if (datoAttr.EsLlavePrimaria)
                                            {
                                                throw new Exception("El  de dato para: " + campo.Name + ", es obligatorio por pertenecer al identificador del registro.");
                                            }
                                        }
                                    }
                                }
                                else
                                {
                                    laPrimariaEsidentidad = true;
                                }
                            }
                        }
                        consultaCampos = consultaCampos.Remove(consultaCampos.Length - 2);
                        consultaValores = consultaValores.Remove(consultaValores.Length - 2);
                    }
                }
            }

            string consulta = "INSERT INTO " + consultaNombreTabla + " (" + consultaCampos + ") VALUES (" + consultaValores + ")";
            if (laPrimariaEsidentidad)
            {
                consulta += " SELECT @@Identity";
            }

            return this.ejecutarOperacionSQL(consulta, datos, laPrimariaEsidentidad);
        }

        private List<Object> ejecutarConsultaSQL(string consulta, Dictionary<string, object> datos, Type tipo)
        {
            if (this.oConn.State == ConnectionState.Closed)
            {
                this.oConn.ConnectionString = this.cDeConexion;
            }
            using (this.oConn)
            {
                DbCommand oCmd = new SqlCommand(consulta, this.oConn);
                if (datos.Count > 0)
                {
                    foreach (KeyValuePair<string, Object> dato in datos)
                    {
                        SqlParameter param = new SqlParameter();
                        param.ParameterName = dato.Key;
                        param.Value = dato.Value;
                        oCmd.Parameters.Add(param);
                    }
                }
                if (this.oConn.State == ConnectionState.Closed)
                {
                    this.oConn.Open();
                }
                IDataReader t = oCmd.ExecuteReader();
                List<Object> records = new List<object>();

                while (t.Read())
                {
                    records.Add(llenarDatos(t, tipo));
                }

                return records;
            }
        }

        private Object llenarDatos(IDataReader datos, Type objeto)
        {
            Object nuevo = Activator.CreateInstance(objeto);
            PropertyInfo[] campos = objeto.GetProperties();
            foreach (PropertyInfo campo in campos)
            {
                Tipo datoAttr = (Tipo)System.Attribute.GetCustomAttribute(campo, typeof(Libreria.DAL.Tipo));
                if (datoAttr != null)
                {
                    Object valor = datos[this.validarNombrecampo(campo, datoAttr)];
                    if (!DBNull.Value.Equals(valor))
                    {
                        if (campo.PropertyType.ToString() == "System.Nullable`1[System.Double]")
                        {
                            Double temp;
                            double.TryParse(valor.ToString(), out temp);
                            Double? valorTmp = temp;
                            campo.SetValue(nuevo, valorTmp, null);
                        }
                        else if (campo.PropertyType.ToString() == "System.Nullable`1[System.Int32]")
                        {
                            Int32 temp;
                            Int32.TryParse(valor.ToString(), out temp);
                            Int32? valorTmp = temp;
                            campo.SetValue(nuevo, valorTmp, null);
                        }
                        else
                        {
                            campo.SetValue(nuevo, valor, null);
                        }
                    }
                }
            }
            return nuevo;
        }

        private Int16 ejecutarOperacionSQL(string consulta, Dictionary<string, object> datos, bool devuelveIdentidad)
        {
            using (this.oConn = new SqlConnection(this.cDeConexion))
            {
                SqlCommand cmd = new SqlCommand(consulta, this.oConn);
                foreach (KeyValuePair<string, Object> dato in datos)
                {
                    cmd.Parameters.AddWithValue(dato.Key, dato.Value);
                }
                this.oConn.Open();
                Int16 t;
                if (devuelveIdentidad)
                {
                    t = Convert.ToInt16(cmd.ExecuteScalar());
                }
                else
                {
                    t = Convert.ToInt16(cmd.ExecuteNonQuery());
                }
                this.oConn.Close();
                return t;
            }
        }

        private string validarNombrecampo(PropertyInfo campo, Tipo atributo)
        {
            return (atributo.SsNombreCampoIgualNombreAtributo) ? campo.Name : atributo.SsNombreCampo;
        }

        private bool validaTipoValor(Tipo.TiposSQLServer tipo, Object objeto, Object modelo)
        {
            bool retorna = true;
            string valor = objeto.ToString().Split(' ')[0];
            switch (tipo)
            {
                case Tipo.TiposSQLServer._varchar:
                    if (valor == "System.String")
                    {
                        if ((string)((PropertyInfo)objeto).GetValue(modelo, null) == null)
                        {
                            retorna = false;
                        }

                    }
                    else
                    {
                        throw new Exception("El tipo de dato para varchar debe ser string");
                    }

                    break;
                case Tipo.TiposSQLServer._datetime:
                    if (valor == "System.DateTime")
                    {
                        if ((DateTime)((PropertyInfo)objeto).GetValue(modelo, null) == DateTime.MinValue)
                        {
                            retorna = false;
                        }

                    }
                    else
                    {
                        throw new Exception("El tipo de dato para datetime debe ser datetime");
                    }

                    break;
                case Tipo.TiposSQLServer._int:
                    if (valor != "System.Nullable`1[System.Int32]")
                    {
                        throw new Exception("El tipo de dato para int debe ser Nullable<Int32>");
                    }
                    else
                    {
                        if ((Int32?)((PropertyInfo)objeto).GetValue(modelo, null) == null)
                        {
                            retorna = false;
                        }
                    }
                    break;
                case Tipo.TiposSQLServer._real:
                    if (valor != "System.Nullable`1[System.Double]")
                    {
                        throw new Exception("El tipo de dato para real debe ser Nullable<Double>");
                    }
                    else
                    {
                        if ((Double?)((PropertyInfo)objeto).GetValue(modelo, null) == null)
                        {
                            retorna = false;
                        }
                    }

                    break;
                case Tipo.TiposSQLServer._bit:
                    if (valor != "System.Nullable`1[System.Boolean]")
                    {
                        throw new Exception("El tipo de dato para bit debe ser Nullable<Boolean>");
                    }
                    else
                    {
                        if ((Boolean?)((PropertyInfo)objeto).GetValue(modelo, null) == null)
                        {
                            retorna = false;
                        }
                    }
                    break;
                default:
                    throw new Exception("El tipo de dato -" + tipo.ToString() + "- no esta implementado");
            }
            return retorna;
        }

        // Solicitud de instancia del Singleton
        public static MOR Instance(string cadenaDeConexion)
        {
            if (instance == null || instance.oConn == null)
            {
                lock (syncRoot)
                {
                    if (instance == null || instance.oConn == null)
                    {
                        instance = new MOR(cadenaDeConexion);
                    }
                }
            }
            else
            {

                if (string.IsNullOrEmpty( instance.oConn.ConnectionString))
                {
                    instance.oConn.ConnectionString = cadenaDeConexion;
                }
            }

            return instance;
        }

    }
}
